/**
 * 功能： LED智能灯控制系统 设备端1
 * 作者： 单片机菜鸟哥
 * 时间： 2020-03-01
 * 通信方式：使用HTTPS认证再连接
 *
 * 详细描述：
 *   1、esp8266连接上wifi热点
 *   2、请求阿里云https认证设备，获取mqtt配置信息
 *   2、通过mqtt client方式连接上阿里云物联网平台
 *   3、MQTT fx客户端发送控制LED指令到设备端，达到控制亮灭的作用
 *
 */
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <WiFiClientSecure.h>
#include <Adafruit_MQTT.h>
#include <Adafruit_MQTT_Client.h>
#include "utils_hmac.h"

#define DEBUG //是否开启debug功能

#ifdef DEBUG
#define DebugBegin(b)    Serial.begin(b)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
#else
#define DebugPrintln(message)
#define DebugPrint(message)
#endif

const char* WIFI_SSID     = "TP-LINK_5344"; // WiFi账号密码
const char* WIFI_PASSWORD = "6206908you11011010"; // WiFi密码

/******************产品/设备配置(每个人需要根据自己的产品设备信息去动态更换)**********************/
#define REGION_ID   "cn-shanghai"  //地域
#define PRODUCT_KEY "a1yPA4CufD2"  //产品key  从产品详情获取
#define DEVICE_NAME "LED_Device1"  //设备deviceName  从设备详情获取
#define DEVICE_SECRET "Di5EGEbrB9TDloWhQ87POx1nrDsfb41I" //对应的设备秘钥
// 服务端相关
#define MQTT_SERVER   PRODUCT_KEY ".iot-as-mqtt." REGION_ID ".aliyuncs.com" //阿里云MQTT服务地址
#define MQTT_PORT     1883                                               //MQTT服务端口
// 相关主题 自定义一个主题
#define TOPIC1 "/" PRODUCT_KEY "/" DEVICE_NAME "/user/setledstatus"

#define HTTPS_SERVER "iot-auth." REGION_ID ".aliyuncs.com" //认证地址
#define HTTPS_PORT 443
/*************************************************************************************************/
static char MQTT_CLIENT_ID[110]; // Mqtt ClientID
static char MQTT_USERNAME[40];// Mqtt UserName
static char MQTT_PASSWORD[50];     // Mqtt Psw

WiFiClientSecure client_s;
Adafruit_MQTT_Client mqttClient(&client_s, MQTT_SERVER, MQTT_PORT, MQTT_CLIENT_ID, MQTT_USERNAME, MQTT_PASSWORD);
Adafruit_MQTT_Subscribe ledstatus = Adafruit_MQTT_Subscribe(&mqttClient , TOPIC1);

void doWiFiTick();
void connectToMqtt();
bool connectHTTPS();
bool loadROOTcert();
void deviceAuthen();
void sendHTTPSrequest();
void readHTTPSreponse();
void onLedStatusCallback(char *data, uint16_t len);
void parseMqttResponse(char* payload);

void setup() {
  // put your setup code here, to run once:
  delay(2000);
  DebugBegin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  client_s.setInsecure();
}

void loop() {
  // put your main code here, to run repeatedly:
  ESP.wdtFeed();
  doWiFiTick();
  if (WiFi.status() == WL_CONNECTED) {
    connectToMqtt();

//    Adafruit_MQTT_Subscribe *subscription;
//    if ((subscription = mqttClient.readSubscription(10))) {
//      if (subscription == &ledstatus) {
//        Serial.print(F("Got: "));
//        Serial.println((char *)ledstatus.lastread);
//      }
//    }
    mqttClient.processPackets(1000);
    if(! mqttClient.ping()) {
       mqttClient.disconnect();
    }
  }
  delay(2000);
}

/**
 * 功能：连接路由心跳函数
 */
void doWiFiTick() {
  static bool taskStarted = false;
  static bool startSTAFlag = false;
  static uint32_t lastWiFiCheckTick = 0;

  if (!startSTAFlag) {
    startSTAFlag = true;

    DebugPrint("connect to ap:");
    DebugPrintln(WIFI_SSID);
    WiFi.disconnect();
    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  }

  //未连接1s重连
  if ( WiFi.status() != WL_CONNECTED ) {
    if (millis() - lastWiFiCheckTick > 1000) {
      lastWiFiCheckTick = millis();
      DebugPrint(".");
    }
  }
  //连接成功建立
  else {
    if (taskStarted == false) {
      taskStarted = true;
      DebugPrint("\r\nGet IP Address: ");
      DebugPrintln(WiFi.localIP());
      deviceAuthen();
    }
  }
}

/**
 * 生成 HmacSha1
 * @param sign HmacSha1字符串
 */
static ICACHE_FLASH_ATTR char* parseHmacSha1(char *sign) {//
  String chipidStr = String(ESP.getChipId(), HEX);//"ef63aa";
  String clientIDStr = String(PRODUCT_KEY) + "." + chipidStr;
  char *hmac_source;//[120];
  strcpy(MQTT_CLIENT_ID, clientIDStr.c_str());
  String str = "clientId" + clientIDStr + "deviceName" + DEVICE_NAME + "productKey" + PRODUCT_KEY;
  uint8_t str_len = str.length() + 1;
  
  hmac_source = (char*)malloc(str_len * sizeof(char));
  memcpy(hmac_source, str.c_str(), str_len);

  DebugPrint(F("hmac_source: "));
  DebugPrintln(hmac_source);

  utils_hmac_sha1(hmac_source, strlen(hmac_source), sign, DEVICE_SECRET, strlen(DEVICE_SECRET));

  DebugPrint(F("signature: "));
  DebugPrintln(sign);

  free(hmac_source);
  return sign;
}

/***
 * 发送验证请求
 */
void ICACHE_FLASH_ATTR sendHTTPSrequest() {
  char signature[40];
  parseHmacSha1(signature);
  String signatureStr = "";
  
  for (byte i = 0 ; i < 40; i++) {
    signatureStr += signature[i];
  }
  DebugPrint(F("signatureStr: "));
  DebugPrintln(signatureStr);
 
  const char* host = "iot-auth.cn-shanghai.aliyuncs.com";

//  String chipidStr = String(ESP.getChipId(), HEX);//"ef63aa";//
//  String clientIDStr = String(PRODUCT_KEY) + "." + chipidStr;

  String str = String("productKey=") + PRODUCT_KEY + "&deviceName=" + DEVICE_NAME + "&signmethod=hmacsha1&sign=" + signatureStr +
               "&version=default&clientId=" + MQTT_CLIENT_ID + "&resources=mqtt";

  String str1 = (String("POST ") + "/auth/devicename HTTP/1.1\r\nHost: " + HTTPS_SERVER + "\r\n" +
                 "Accept: text/xml,text/javascript,text/html,application/json\r\nContent-Type: application/x-www-form-urlencoded\r\n" +
                 "Content-Length: " + str.length() + "\r\n\r\n");
  client_s.print(str1);
  DebugPrint(str1);

  client_s.print(str);
  DebugPrintln(str);

  DebugPrint(F("request sent!"));
  delay(100);
}

/**
 * 处理认证请求
 */
void ICACHE_FLASH_ATTR readHTTPSreponse() {
  while (client_s.connected())   {
    String line = client_s.readStringUntil('\n');
    if (line == "\r") {
      DebugPrintln(F("=========="));
      break;
    }
  }
  String resstring = client_s.readStringUntil('\n');

  DebugPrint(F("receiving: "));
  DebugPrintln(resstring);

  DynamicJsonBuffer jsonBuffer;
  JsonObject& root = jsonBuffer.parseObject(resstring);

  // Test if parsing succeeds.
  if (!root.success()) {
     DebugPrintln("parseObject() failed");
     return;
  }

  strcpy(MQTT_USERNAME, root["data"]["iotId"]);
  strcpy(MQTT_PASSWORD, root["data"]["iotToken"]);


  DebugPrint(F("MQTT_CLIENT_ID: "));
  DebugPrintln(MQTT_CLIENT_ID);
  DebugPrint(F("MQTT_USERNAME: "));
  DebugPrintln(MQTT_USERNAME);
  DebugPrint(F("MQTT_PASSWORD: "));
  DebugPrintln(MQTT_PASSWORD);

  DebugPrintln(F("=========="));
  client_s.flush();
}

/*
 * 设备认证
 */
void deviceAuthen() {
  while (loadROOTcert()) {
    while (connectHTTPS()) {
      sendHTTPSrequest();
      readHTTPSreponse();
      return;
    }
    ESP.wdtFeed();
  }
}

/*
 * 连接到远程服务器并且获取状态
 */
bool ICACHE_FLASH_ATTR connectHTTPS() {
  DebugPrint("connecting to ");
  DebugPrintln(HTTPS_SERVER);
  if (!client_s.connect(HTTPS_SERVER, HTTPS_PORT)) {
    DebugPrintln(F("connection failed"));
    return false;
  }else {
    DebugPrintln(F("connection succeed"));
    return true;
  }
}

/*
 * 设置根证书并且返回状态
 */
bool ICACHE_FLASH_ATTR loadROOTcert() {
//  bool res = client_s.setCACert(caCert, caCertLen);
//  if (!res) {
//    Serial.println(F("Failed to load root CA certificate!"));
//    while (true) {
//      yield();
//    }
//  }
//  else {
//    Serial.println(F("succeed to load root CA certificate!"));
//  }
  return true;
}

/**
 * 功能：连接到Mqtt
 */
void connectToMqtt() {
  if (mqttClient.connected()) {
   return;
  }

  DebugPrint(F("Connecting to MQTT... "));

  int8_t ret;
  uint8_t retries = 3;

  while ((ret = mqttClient.connect(MQTT_USERNAME, MQTT_PASSWORD)) != 0) { // connect will return true for connected
      DebugPrintln(mqttClient.connectErrorString(ret));
      DebugPrintln(F("Retrying MQTT connection in 5 seconds..."));
      mqttClient.disconnect();
      delay(5000);  // wait 5 seconds
      retries--;
      if (retries == 0) {
        // basically die and wait for WDT to reset me
        while (1);
      }
      yield();
  }
  DebugPrintln(F("MQTT Connected!"));
  // 订阅主题
  ledstatus.setCallback(onLedStatusCallback);
  mqttClient.subscribe(&ledstatus);
}

void ICACHE_FLASH_ATTR onLedStatusCallback(char *data, uint16_t len) {
  DebugPrintln(data);
  parseMqttResponse(data);
}

/**
 * 解析mqtt数据
 */
void parseMqttResponse(char* payload){
   DebugPrintln("start parseMqttResponse");
   StaticJsonBuffer<100> jsonBuffer;
     // StaticJsonBuffer 在栈区分配内存   它也可以被 DynamicJsonBuffer（内存在堆区分配） 代替
     // DynamicJsonBuffer  jsonBuffer;
   JsonObject& root = jsonBuffer.parseObject(payload);

     // Test if parsing succeeds.
   if (!root.success()) {
       DebugPrintln("parseObject() failed");
       return ;
   }

   String deviceName = root["dn"];
   int status = root["s"];

   if(strcmp(DEVICE_NAME,deviceName.c_str())== 0){
        if (status == 1) {
            digitalWrite(LED_BUILTIN, LOW);
        } else {
            digitalWrite(LED_BUILTIN, HIGH);
        }
    }
}
